Spatially Continuous Data IV
NOTE: You can download the source files for this book from here. The source files are in the format of R Notebooks. Notebooks are pretty neat, because the allow you execute code within the notebook, so that you can work interactively with the notes.
In the previous practice you were introduced to the concept of variographic analysis for fields/spatially continuous data.
If you wish to work interactively with this chapter you will need the following:
Learning objectives
In this practice, you will learn:
- Using residual spatial pattern to estimate prediction errors.
- Kriging: a method for optimal predictions.
Suggested reading
- Bailey TC and Gatrell AC [-@Bailey1995] Interactive Spatial Data Analysis, Chapters 5 and 6. Longman: Essex.
- Bivand RS, Pebesma E, and Gomez-Rubio V [-@Bivand2008] Applied Spatial Data Analysis with R, Chapter 8. Springer: New York.
- Brunsdon C and Comber L [-@Brunsdon2015R] An Introduction to R for Spatial Analysis and Mapping, Chapter 6, Sections 6.7 and 6.8. Sage: Los Angeles.
- Isaaks EH and Srivastava RM [-@Isaaks1989applied] An Introduction to Applied Geostatistics, CHAPTERS. Oxford University Press: Oxford.
- O’Sullivan D and Unwin D [-@Osullivan2010] Geographic Information Analysis, 2nd Edition, Chapters 9 and 10. John Wiley & Sons: New Jersey.
Preliminaries
As usual, it is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:
rm(list = ls())
Note that ls() lists all objects currently on the worspace.
Load the libraries you will use in this activity:
library(tidyverse)
library(spdep)
library(plotly)
Attaching package: 㤼㸱plotly㤼㸲
The following object is masked from 㤼㸱package:ggplot2㤼㸲:
last_plot
The following object is masked from 㤼㸱package:stats㤼㸲:
filter
The following object is masked from 㤼㸱package:graphics㤼㸲:
layout
library(gstat)
library(geog4ga3)
Begin by loading the data file:
data("Walker_Lake")
You can verify the contents of the dataframe:
summary(Walker_Lake)
ID X Y V U T
Length:470 Min. : 8.0 Min. : 8.0 Min. : 0.0 Min. : 0.00 1: 45
Class :character 1st Qu.: 51.0 1st Qu.: 80.0 1st Qu.: 182.0 1st Qu.: 83.95 2:425
Mode :character Median : 89.0 Median :139.5 Median : 425.2 Median : 335.00
Mean :111.1 Mean :141.3 Mean : 435.4 Mean : 613.27
3rd Qu.:170.0 3rd Qu.:208.0 3rd Qu.: 644.4 3rd Qu.: 883.20
Max. :251.0 Max. :291.0 Max. :1528.1 Max. :5190.10
NA's :195
Using residual spatial pattern to estimate prediction errors
Previously, you have seen how to interpolate a field using trend surface analysis. You have also seen how that may at least on occasions residuals that are not spatially independent.
The implication of non-random residuals is that there uncaptured pattern remains. Which means that information can still be extracted from it. Lets revisit the case of Walker Lake to explore one way of doing this.
As before, we first calculate the polynomial terms of the coordinates to fit a trend surface to the data:
Walker_Lake <- mutate(Walker_Lake,
X3 = X^3, X2Y = X^2 * Y, X2 = X^2,
XY = X * Y,
Y2 = Y^2, XY2 = X * Y^2, Y3 = Y^3)
Given the polynomial expansion, we can proceed to estimate the following cubic trend surface model, which in the previous practice provided the best fit to the data:
WL.trend3 <- lm(formula = V ~ X3 + X2Y + X2 + X + XY + Y + Y2 + XY2 + Y3,
data = Walker_Lake)
summary(WL.trend3)
Call:
lm(formula = V ~ X3 + X2Y + X2 + X + XY + Y + Y2 + XY2 + Y3,
data = Walker_Lake)
Residuals:
Min 1Q Median 3Q Max
-564.19 -197.41 7.91 194.25 929.72
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -8.620e+00 1.227e+02 -0.070 0.944035
X3 1.533e-04 4.806e-05 3.190 0.001522 **
X2Y 6.139e-05 3.909e-05 1.570 0.117000
X2 -6.651e-02 1.838e-02 -3.618 0.000330 ***
X 9.172e+00 2.386e+00 3.844 0.000138 ***
XY -4.420e-02 1.430e-02 -3.092 0.002110 **
Y 4.794e+00 2.040e+00 2.350 0.019220 *
Y2 -1.806e-03 1.327e-02 -0.136 0.891822
XY2 7.679e-05 2.956e-05 2.598 0.009669 **
Y3 -4.170e-05 2.819e-05 -1.479 0.139759
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 276.7 on 460 degrees of freedom
Multiple R-squared: 0.1719, Adjusted R-squared: 0.1557
F-statistic: 10.61 on 9 and 460 DF, p-value: 5.381e-15
Lets visualize the residuals. As you can see, the residuals do not appear to be random
plot_ly(x = ~Walker_Lake$X, y = ~Walker_Lake$Y, z = ~WL.trend3$residuals, color = ~WL.trend3$residuals < 0, colors = c("blue", "red"), type = "scatter3d")
No scatter3d mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
No scatter3d mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
Now create an interpolation grid.
X.p <- seq(from = 0.1, to = 255.1, by = 2.5)
Y.p <- seq(from = 0.1, to = 295.1, by = 2.5)
df.p <- expand.grid(X = X.p, Y = Y.p)
to which we can add the polynomial terms:
df.p <- mutate(df.p, X3 = X^3, X2Y = X^2 * Y, X2 = X^2,
XY = X * Y,
Y2 = Y^2, XY2 = X * Y^2, Y3 = Y^3)
The interpolated cubic surface is obtained as:
WL.preds3 <- predict(WL.trend3, newdata = df.p, se.fit = TRUE, interval = "prediction", level = 0.95)
The surface is converted into a matrix for 3D plotting:
z.p3 <- matrix(data = WL.preds3$fit[,1], nrow = length(Y.p), ncol = length(X.p), byrow = TRUE)
And plot:
WL.plot3 <- plot_ly(x = ~X.p, y = ~Y.p, z = ~z.p3,
type = "surface", colors = "YlOrRd") %>%
layout(scene = list(
aspectmode = "manual", aspectratio = list(x = 1, y = 1, z = 1)))
WL.plot3
The trend surface provides a smooth estimate of the field. However, it is not sufficient to capture all systematic variation, and fails to produce random residuals.
A possible way of enhancing this approach to interpolation is to exploit the information that remains in the residuals, for instance by the use of \(k\)-point means.
Lets see how this is done.
To interpolate the residuals, we first need the set of target points (the points for the interpolation), as well as the source (the observations)m as follows:
target_xy = expand.grid(x = X.p, y = Y.p)
source_xy = cbind(x = Walker_Lake$X, y = Walker_Lake$Y)
It is possible now to use the kpointmean function to interpolate the residuals, for instance using \(k=5\) neighbors:
kpoint.5 <- kpointmean(source_xy = source_xy, z = WL.trend3$residuals, target_xy = target_xy, k = 5)
Given the estimated residuals, we can add them to the cubic trend surface, as follows:
z.p3 <- matrix(data = WL.preds3$fit[,1] + kpoint.5$z,
nrow = length(Y.p), ncol = length(X.p), byrow = TRUE)
to produce an interpolated field:
WL.plot3 <- plot_ly(x = ~X.p, y = ~Y.p, z = ~z.p3,
type = "surface", colors = "YlOrRd") %>%
layout(scene = list(
aspectmode = "manual", aspectratio = list(x = 1, y = 1, z = 1)))
WL.plot3
Of all the approaches you have seen so far, this is the first that provides a genuine estimate of the following: \[
\hat{z}_p + \hat{\epsilon}_p
\]
With trend surface analysis providing a smooth estimator of the underlying field: \[
\hat{z}_p = f(x_p, y_p)
\]
And \(k\)-point means providing an estimator of: \[
\hat{\epsilon}_p
\]
A question is how to decide the number of neighbors to use in the calculation of the k-point means. As previously discussed, \(k = 1\) becomes identical to Voronoi polygons, and \(k = n\) becomes the global mean.
A second question concerns the way the average is calculated. As variographic analysis demonstrates, it is possible to estimate the way in which spatial dependence weakens with distance. Why should more distant points be weighted equally? The answer is, there is no reason why they should, and in fact, variographic analysis elegantly solves this, as well the question of how many points to use: all of them, with varying weights.
Next, we will introduce kriging, a method for optimal prediction that is based on the use of variographic analyisis.
Kriging: a method for optimal prediction.
Lets begin by positing a situation as follows: \[
\hat{z}_p + \hat{\epsilon}_p = \hat{f}(x_p, y_p) + \hat{\epsilon}_p
\]
where \(\hat{f}(x_p, y_p)\) is a smooth estimator of an underlying field.
We aim to predict \(\hat{\epsilon}_p\) based on the observed residuals. We use an expression similar to the one used for IDW and k-point means (we will use \(\lambda\) for the weights to avoid confusing the the weights in variographic analysis): \[
\hat{\epsilon}_p = \sum_{i=1}^n {\lambda_{pi}\epsilon_i}
\]
That is, \(\hat{\epsilon}_p\) is a linear combination of the prediction residuals from the trend: \[
\epsilon_i = z_i - \hat{f}(x_i, y_i)
\]
Since the residuals are random, the prediction residuals are random too, and it is possible to define the following expected mean squared error, or prediction variance: \[
\sigma_{\epsilon}^2 = E[(\hat{\epsilon}_p - \epsilon_i)^2]
\]
The prediction variance measures how close, on average, the prediction error is to the residuals.
The prediction variance can be decomposed as follows: \[
\sigma_{\epsilon}^2 = E[\hat{\epsilon}_p] + E[\epsilon_i] - 2E[\hat{\epsilon_i\epsilon}_p]
\]
It turns out (we will not show de detailed derivation, but it can be consulted here), that the expresion for the prediction variance depends on the weights: \[
\sigma_{\epsilon}^2 = \sum_{i=1}^n \sum_{j=1}^n{\lambda_{ip}\lambda_{jp}C_{ij}} + \sigma^2 + 2\sum_{i=1}^{n}{\lambda_{ip}C_{ip}}
\] where \(C_{ij}\) is the autocovariance between observations at \(i\) and \(j\), and \(C_{ip}\) is the autocovariance between the observation at \(i\) and prediction location \(p\).
Fortunately for us, the semivariogram and the autocovariance is straightforward: \[
C_{z}(h) =\sigma^2 - \hat{\gamma}_{z}(h)
\]
This means that, given the distance \(h\) between \(i\) and \(j\), and \(i\) and \(p\), we can use a semivariogram to obtain the autocovariances needed to calculate the prediction variance. We are still missing, however, the weights \(\lambda\), which are not known a priori.
These weights can be obtained if we use the following rules:
The expectation of the prediction errors is zero (unbiassedness) Find the weights \(lambda\) that minimize the prediction variance (optimal estimator).
This makes sense, since we would like our predictions to be unbiased and as precise as possible, that is, to have the smallest variance.
Again, solving the minimization problem is beyond the scope of this practice, but it suffices to say that the results are as follows: \[
\mathbf{\lambda}_p = \mathbf{C}^{-1}\mathit{c}_{p}
\] where \(\mathbf{C}\) is the covariance matrix, and \(\mathit{c}_{p}\) is the covariance vector for location \(p\).
Kriging is known to have the properties of Best (in the sense that it minimizes the variance) Linear (because of predictions are a linear combination of weights) Unbiased (since the estimators of the prediction errors are zero) Estimator, or BLUP.
Kriging is implemented in the package gstat as follows.
Lets first conduct variographic analysis of the residuals. The function variogram uses as an argument a SpatialPointsDataFrame that we can create as follows, based on the tidy table:
Walker_Lake.sp <- Walker_Lake
coordinates(Walker_Lake.sp) <- ~X+Y
The variogram of the residuals can be obtained by specifying a trend surface in the formula:
variogram_v <- variogram(V ~ X3 + X2Y + X2 + X + XY + Y + Y2 + XY2 + Y3,
data = Walker_Lake.sp)
ggplot(data = variogram_v, aes(x = dist, y = gamma)) +
geom_point() +
geom_text(aes(label = np), nudge_y = -1500) +
xlab("Distance") + ylab("Semivariance")

You can verify that the semivariogram above corresponds to the residuals by repeating the analysis directly on the residuals. First join the residuals to the SpatialPointsDataFrame:
Walker_Lake.sp$e <- WL.trend3$residuals
And then calculate the semivariogram and plot:
variogram_e <- variogram(e ~ 1,
data = Walker_Lake.sp)
ggplot(data = variogram_e, aes(x = dist, y = gamma)) +
geom_point() +
geom_text(aes(label = np), nudge_y = -1500) +
xlab("Distance") + ylab("Semivariance")

The empirical semivariogram is used to estimate a semivariogram function:
variogram_v.t <- fit.variogram(variogram_v, model = vgm("Exp", "Sph", "Gau"))
variogram_v.t
The variogram function plots as follows:
gamma.t <- variogramLine(variogram_v.t, maxdist = 130)
ggplot(data = variogram_v, aes(x = dist, y = gamma)) +
geom_point(size = 3) +
geom_line(data = gamma.t, aes(x = dist, y = gamma)) +
xlab("Distance") + ylab("Semivariance")

df.sp <- df.p
coordinates(df.sp) <- ~X+Y
V.kriged <- krige(V ~ X3 + X2Y + X2 + X + XY + Y + Y2 + XY2 + Y3,
Walker_Lake.sp, df.sp, variogram_v.t)
[using universal kriging]
Extract the predictions and prediction variance from the object V.kriged:
V.km <- matrix(data = V.kriged$var1.pred,
nrow = 119, ncol = 103, byrow = TRUE)
V.sm <- matrix(data = V.kriged$var1.var,
nrow = 119, ncol = 103, byrow = TRUE)
You can now plot the interpolated field:
V.km.plot <- plot_ly(x = ~X.p, y = ~Y.p, z = ~V.km,
type = "surface", colors = "YlOrRd") %>%
layout(scene = list(
aspectmode = "manual", aspectratio = list(x = 1, y = 1, z = 1)))
V.km.plot
Also, you can plot the kriging standard errors (the square root of the prediction variance). This gives an estimate of the uncertainty in the predictions:
V.sm.plot <- plot_ly(x = ~X.p, y = ~Y.p, z = ~sqrt(V.sm),
type = "surface", colors = "YlOrRd") %>%
layout(scene = list(
aspectmode = "manual", aspectratio = list(x = 1, y = 1, z = 1)))
V.sm.plot
Where are predictions more/less precise?
LS0tDQp0aXRsZTogIlNwYXRpYWxseSBDb250aW51b3VzIERhdGEgSVYiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIFNwYXRpYWxseSBDb250aW51b3VzIERhdGEgSVYNCg0KKk5PVEUqOiBZb3UgY2FuIGRvd25sb2FkIHRoZSBzb3VyY2UgZmlsZXMgZm9yIHRoaXMgYm9vayBmcm9tIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vcGFlemhhL1NwYXRpYWwtU3RhdGlzdGljcy1Db3Vyc2UpLiBUaGUgc291cmNlIGZpbGVzIGFyZSBpbiB0aGUgZm9ybWF0IG9mIFIgTm90ZWJvb2tzLiBOb3RlYm9va3MgYXJlIHByZXR0eSBuZWF0LCBiZWNhdXNlIHRoZSBhbGxvdyB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHNvIHRoYXQgeW91IGNhbiB3b3JrIGludGVyYWN0aXZlbHkgd2l0aCB0aGUgbm90ZXMuIA0KDQpJbiB0aGUgcHJldmlvdXMgcHJhY3RpY2UgeW91IHdlcmUgaW50cm9kdWNlZCB0byB0aGUgY29uY2VwdCBvZiB2YXJpb2dyYXBoaWMgYW5hbHlzaXMgZm9yIGZpZWxkcy9zcGF0aWFsbHkgY29udGludW91cyBkYXRhLg0KDQpJZiB5b3Ugd2lzaCB0byB3b3JrIGludGVyYWN0aXZlbHkgd2l0aCB0aGlzIGNoYXB0ZXIgeW91IHdpbGwgbmVlZCB0aGUgZm9sbG93aW5nOg0KDQoqIEFuIFIgbWFya2Rvd24gbm90ZWJvb2sgdmVyc2lvbiBvZiB0aGlzIGRvY3VtZW50ICh0aGUgc291cmNlIGZpbGUpLg0KDQoqIEEgcGFja2FnZSBjYWxsZWQgYGdlb2c0Z2EzYC4NCg0KIyMgTGVhcm5pbmcgb2JqZWN0aXZlcw0KDQpJbiB0aGlzIHByYWN0aWNlLCB5b3Ugd2lsbCBsZWFybjoNCg0KMS4gVXNpbmcgcmVzaWR1YWwgc3BhdGlhbCBwYXR0ZXJuIHRvIGVzdGltYXRlIHByZWRpY3Rpb24gZXJyb3JzLg0KMi4gS3JpZ2luZzogYSBtZXRob2QgZm9yIG9wdGltYWwgcHJlZGljdGlvbnMuDQoNCiMjIFN1Z2dlc3RlZCByZWFkaW5nDQoNCi0gQmFpbGV5IFRDIGFuZCBHYXRyZWxsIEFDIFstQEJhaWxleTE5OTVdIEludGVyYWN0aXZlIFNwYXRpYWwgRGF0YSBBbmFseXNpcywgQ2hhcHRlcnMgNSBhbmQgNi4gTG9uZ21hbjogRXNzZXguDQotIEJpdmFuZCBSUywgUGViZXNtYSBFLCBhbmQgR29tZXotUnViaW8gViBbLUBCaXZhbmQyMDA4XSBBcHBsaWVkIFNwYXRpYWwgRGF0YSBBbmFseXNpcyB3aXRoIFIsIENoYXB0ZXIgOC4gU3ByaW5nZXI6IE5ldyBZb3JrLg0KLSBCcnVuc2RvbiBDIGFuZCBDb21iZXIgTCBbLUBCcnVuc2RvbjIwMTVSXSBBbiBJbnRyb2R1Y3Rpb24gdG8gUiBmb3IgU3BhdGlhbCBBbmFseXNpcyBhbmQgTWFwcGluZywgQ2hhcHRlciA2LCBTZWN0aW9ucyA2LjcgYW5kIDYuOC4gU2FnZTogTG9zIEFuZ2VsZXMuDQotIElzYWFrcyBFSCBhbmQgU3JpdmFzdGF2YSBSTSAgWy1ASXNhYWtzMTk4OWFwcGxpZWRdIEFuIEludHJvZHVjdGlvbiB0byBBcHBsaWVkIEdlb3N0YXRpc3RpY3MsICoqQ0hBUFRFUlMqKi4gT3hmb3JkIFVuaXZlcnNpdHkgUHJlc3M6IE94Zm9yZC4NCi0gTydTdWxsaXZhbiBEIGFuZCBVbndpbiBEIFstQE9zdWxsaXZhbjIwMTBdIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gQW5hbHlzaXMsIDJuZCBFZGl0aW9uLCBDaGFwdGVycyA5IGFuZCAxMC4gSm9obiBXaWxleSAmIFNvbnM6IE5ldyBKZXJzZXkuDQoNCiMjIFByZWxpbWluYXJpZXMNCg0KQXMgdXN1YWwsIGl0IGlzIGdvb2QgcHJhY3RpY2UgdG8gY2xlYXIgdGhlIHdvcmtpbmcgc3BhY2UgdG8gbWFrZSBzdXJlIHRoYXQgeW91IGRvIG5vdCBoYXZlIGV4dHJhbmVvdXMgaXRlbXMgdGhlcmUgd2hlbiB5b3UgYmVnaW4geW91ciB3b3JrLiBUaGUgY29tbWFuZCBpbiBSIHRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgaXMgYHJtYCAoZm9yICJyZW1vdmUiKSwgZm9sbG93ZWQgYnkgYSBsaXN0IG9mIGl0ZW1zIHRvIGJlIHJlbW92ZWQuIFRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgZnJvbSBfYWxsXyBvYmplY3RzLCBkbyB0aGUgZm9sbG93aW5nOg0KYGBge3J9DQpybShsaXN0ID0gbHMoKSkNCmBgYA0KDQpOb3RlIHRoYXQgYGxzKClgIGxpc3RzIGFsbCBvYmplY3RzIGN1cnJlbnRseSBvbiB0aGUgd29yc3BhY2UuDQoNCkxvYWQgdGhlIGxpYnJhcmllcyB5b3Ugd2lsbCB1c2UgaW4gdGhpcyBhY3Rpdml0eToNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNwZGVwKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGdzdGF0KQ0KbGlicmFyeShnZW9nNGdhMykNCmBgYA0KDQpCZWdpbiBieSBsb2FkaW5nIHRoZSBkYXRhIGZpbGU6DQpgYGB7cn0NCmRhdGEoIldhbGtlcl9MYWtlIikNCmBgYA0KDQpZb3UgY2FuIHZlcmlmeSB0aGUgY29udGVudHMgb2YgdGhlIGRhdGFmcmFtZToNCmBgYHtyfQ0Kc3VtbWFyeShXYWxrZXJfTGFrZSkNCmBgYA0KDQojIyBVc2luZyByZXNpZHVhbCBzcGF0aWFsIHBhdHRlcm4gdG8gZXN0aW1hdGUgcHJlZGljdGlvbiBlcnJvcnMNCg0KUHJldmlvdXNseSwgeW91IGhhdmUgc2VlbiBob3cgdG8gaW50ZXJwb2xhdGUgYSBmaWVsZCB1c2luZyB0cmVuZCBzdXJmYWNlIGFuYWx5c2lzLiBZb3UgaGF2ZSBhbHNvIHNlZW4gaG93IHRoYXQgbWF5IGF0IGxlYXN0IG9uIG9jY2FzaW9ucyByZXNpZHVhbHMgdGhhdCBhcmUgbm90IHNwYXRpYWxseSBpbmRlcGVuZGVudC4gDQoNClRoZSBpbXBsaWNhdGlvbiBvZiBub24tcmFuZG9tIHJlc2lkdWFscyBpcyB0aGF0IHRoZXJlIHVuY2FwdHVyZWQgcGF0dGVybiByZW1haW5zLiBXaGljaCBtZWFucyB0aGF0IGluZm9ybWF0aW9uIGNhbiBzdGlsbCBiZSBleHRyYWN0ZWQgZnJvbSBpdC4gTGV0cyByZXZpc2l0IHRoZSBjYXNlIG9mIFdhbGtlciBMYWtlIHRvIGV4cGxvcmUgb25lIHdheSBvZiBkb2luZyB0aGlzLg0KDQpBcyBiZWZvcmUsIHdlIGZpcnN0IGNhbGN1bGF0ZSB0aGUgcG9seW5vbWlhbCB0ZXJtcyBvZiB0aGUgY29vcmRpbmF0ZXMgdG8gZml0IGEgdHJlbmQgc3VyZmFjZSB0byB0aGUgZGF0YToNCmBgYHtyfQ0KV2Fsa2VyX0xha2UgPC0gbXV0YXRlKFdhbGtlcl9MYWtlLA0KICAgICAgICAgICAgICAgICAgICAgICAgWDMgPSBYXjMsIFgyWSA9IFheMiAqIFksIFgyID0gWF4yLCANCiAgICAgICAgICAgICAgICAgICAgICAgIFhZID0gWCAqIFksDQogICAgICAgICAgICAgICAgICAgICAgICBZMiA9IFleMiwgWFkyID0gWCAqIFleMiwgWTMgPSBZXjMpDQpgYGANCg0KR2l2ZW4gdGhlIHBvbHlub21pYWwgZXhwYW5zaW9uLCB3ZSBjYW4gcHJvY2VlZCB0byBlc3RpbWF0ZSB0aGUgZm9sbG93aW5nIGN1YmljIHRyZW5kIHN1cmZhY2UgbW9kZWwsIHdoaWNoIGluIHRoZSBwcmV2aW91cyBwcmFjdGljZSBwcm92aWRlZCB0aGUgYmVzdCBmaXQgdG8gdGhlIGRhdGE6DQpgYGB7cn0NCldMLnRyZW5kMyA8LSBsbShmb3JtdWxhID0gViB+IFgzICsgWDJZICsgWDIgKyBYICsgWFkgKyBZICsgWTIgKyBYWTIgKyBZMywgDQogICAgICAgICAgICAgICAgZGF0YSA9IFdhbGtlcl9MYWtlKQ0Kc3VtbWFyeShXTC50cmVuZDMpDQpgYGANCg0KTGV0cyB2aXN1YWxpemUgdGhlIHJlc2lkdWFscy4gQXMgeW91IGNhbiBzZWUsIHRoZSByZXNpZHVhbHMgZG8gbm90IGFwcGVhciB0byBiZSByYW5kb20gDQpgYGB7cn0NCnBsb3RfbHkoeCA9IH5XYWxrZXJfTGFrZSRYLCB5ID0gfldhbGtlcl9MYWtlJFksIHogPSB+V0wudHJlbmQzJHJlc2lkdWFscywgY29sb3IgPSB+V0wudHJlbmQzJHJlc2lkdWFscyA8IDAsIGNvbG9ycyA9IGMoImJsdWUiLCAicmVkIiksIHR5cGUgPSAic2NhdHRlcjNkIikNCmBgYA0KDQpOb3cgY3JlYXRlIGFuIGludGVycG9sYXRpb24gZ3JpZC4NCmBgYHtyfQ0KWC5wIDwtIHNlcShmcm9tID0gMC4xLCB0byA9IDI1NS4xLCBieSA9IDIuNSkNClkucCA8LSBzZXEoZnJvbSA9IDAuMSwgdG8gPSAyOTUuMSwgYnkgPSAyLjUpDQpkZi5wIDwtIGV4cGFuZC5ncmlkKFggPSBYLnAsIFkgPSBZLnApDQpgYGANCg0KdG8gd2hpY2ggd2UgY2FuIGFkZCB0aGUgcG9seW5vbWlhbCB0ZXJtczoNCmBgYHtyfQ0KZGYucCA8LSBtdXRhdGUoZGYucCwgWDMgPSBYXjMsIFgyWSA9IFheMiAqIFksIFgyID0gWF4yLCANCiAgICAgICAgICAgICAgIFhZID0gWCAqIFksIA0KICAgICAgICAgICAgICAgWTIgPSBZXjIsIFhZMiA9IFggKiBZXjIsIFkzID0gWV4zKQ0KYGBgDQoNClRoZSBpbnRlcnBvbGF0ZWQgY3ViaWMgc3VyZmFjZSBpcyBvYnRhaW5lZCBhczoNCmBgYHtyfQ0KV0wucHJlZHMzIDwtIHByZWRpY3QoV0wudHJlbmQzLCBuZXdkYXRhID0gZGYucCwgc2UuZml0ID0gVFJVRSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIsIGxldmVsID0gMC45NSkNCmBgYA0KDQpUaGUgc3VyZmFjZSBpcyBjb252ZXJ0ZWQgaW50byBhIG1hdHJpeCBmb3IgM0QgcGxvdHRpbmc6DQpgYGB7cn0NCnoucDMgPC0gbWF0cml4KGRhdGEgPSBXTC5wcmVkczMkZml0WywxXSwgbnJvdyA9IGxlbmd0aChZLnApLCBuY29sID0gbGVuZ3RoKFgucCksIGJ5cm93ID0gVFJVRSkNCmBgYA0KDQpBbmQgcGxvdDoNCmBgYHtyfQ0KV0wucGxvdDMgPC0gcGxvdF9seSh4ID0gflgucCwgeSA9IH5ZLnAsIHogPSB+ei5wMywgDQogICAgICAgIHR5cGUgPSAic3VyZmFjZSIsIGNvbG9ycyA9ICJZbE9yUmQiKSAlPiUgDQogIGxheW91dChzY2VuZSA9IGxpc3QoDQogICAgYXNwZWN0bW9kZSA9ICJtYW51YWwiLCBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDEsIHkgPSAxLCB6ID0gMSkpKQ0KV0wucGxvdDMNCmBgYA0KDQpUaGUgdHJlbmQgc3VyZmFjZSBwcm92aWRlcyBhIHNtb290aCBlc3RpbWF0ZSBvZiB0aGUgZmllbGQuIEhvd2V2ZXIsIGl0IGlzIG5vdCBzdWZmaWNpZW50IHRvIGNhcHR1cmUgYWxsIHN5c3RlbWF0aWMgdmFyaWF0aW9uLCBhbmQgZmFpbHMgdG8gcHJvZHVjZSByYW5kb20gcmVzaWR1YWxzLg0KDQpBIHBvc3NpYmxlIHdheSBvZiBlbmhhbmNpbmcgdGhpcyBhcHByb2FjaCB0byBpbnRlcnBvbGF0aW9uIGlzIHRvIF9leHBsb2l0XyB0aGUgaW5mb3JtYXRpb24gdGhhdCByZW1haW5zIGluIHRoZSByZXNpZHVhbHMsIGZvciBpbnN0YW5jZSBieSB0aGUgdXNlIG9mICRrJC1wb2ludCBtZWFucy4NCg0KTGV0cyBzZWUgaG93IHRoaXMgaXMgZG9uZS4NCg0KVG8gaW50ZXJwb2xhdGUgdGhlIF9yZXNpZHVhbHNfLCB3ZSBmaXJzdCBuZWVkIHRoZSBzZXQgb2YgX3RhcmdldF8gcG9pbnRzICh0aGUgcG9pbnRzIGZvciB0aGUgaW50ZXJwb2xhdGlvbiksIGFzIHdlbGwgYXMgdGhlIF9zb3VyY2VfICh0aGUgb2JzZXJ2YXRpb25zKW0gYXMgZm9sbG93czoNCmBgYHtyfQ0KdGFyZ2V0X3h5ID0gZXhwYW5kLmdyaWQoeCA9IFgucCwgeSA9IFkucCkNCnNvdXJjZV94eSA9IGNiaW5kKHggPSBXYWxrZXJfTGFrZSRYLCB5ID0gV2Fsa2VyX0xha2UkWSkNCmBgYA0KDQpJdCBpcyBwb3NzaWJsZSBub3cgdG8gdXNlIHRoZSBga3BvaW50bWVhbmAgZnVuY3Rpb24gdG8gaW50ZXJwb2xhdGUgdGhlIHJlc2lkdWFscywgZm9yIGluc3RhbmNlIHVzaW5nICRrPTUkIG5laWdoYm9yczoNCmBgYHtyfQ0Ka3BvaW50LjUgPC0ga3BvaW50bWVhbihzb3VyY2VfeHkgPSBzb3VyY2VfeHksIHogPSBXTC50cmVuZDMkcmVzaWR1YWxzLCB0YXJnZXRfeHkgPSB0YXJnZXRfeHksIGsgPSA1KQ0KYGBgDQoNCkdpdmVuIHRoZSBlc3RpbWF0ZWQgcmVzaWR1YWxzLCB3ZSBjYW4gYWRkIHRoZW0gdG8gdGhlIGN1YmljIHRyZW5kIHN1cmZhY2UsIGFzIGZvbGxvd3M6DQpgYGB7cn0NCnoucDMgPC0gbWF0cml4KGRhdGEgPSBXTC5wcmVkczMkZml0WywxXSArIGtwb2ludC41JHosDQogICAgICAgICAgICAgICBucm93ID0gbGVuZ3RoKFkucCksIG5jb2wgPSBsZW5ndGgoWC5wKSwgYnlyb3cgPSBUUlVFKQ0KYGBgDQoNCnRvIHByb2R1Y2UgYW4gaW50ZXJwb2xhdGVkIGZpZWxkOg0KYGBge3J9DQpXTC5wbG90MyA8LSBwbG90X2x5KHggPSB+WC5wLCB5ID0gflkucCwgeiA9IH56LnAzLCANCiAgICAgICAgdHlwZSA9ICJzdXJmYWNlIiwgY29sb3JzID0gIllsT3JSZCIpICU+JSANCiAgbGF5b3V0KHNjZW5lID0gbGlzdCgNCiAgICBhc3BlY3Rtb2RlID0gIm1hbnVhbCIsIGFzcGVjdHJhdGlvID0gbGlzdCh4ID0gMSwgeSA9IDEsIHogPSAxKSkpDQpXTC5wbG90Mw0KYGBgDQoNCk9mIGFsbCB0aGUgYXBwcm9hY2hlcyB5b3UgaGF2ZSBzZWVuIHNvIGZhciwgdGhpcyBpcyB0aGUgZmlyc3QgdGhhdCBwcm92aWRlcyBhIGdlbnVpbmUgZXN0aW1hdGUgb2YgdGhlIGZvbGxvd2luZzoNCiQkDQpcaGF0e3p9X3AgKyBcaGF0e1xlcHNpbG9ufV9wDQokJA0KDQpXaXRoIHRyZW5kIHN1cmZhY2UgYW5hbHlzaXMgcHJvdmlkaW5nIGEgc21vb3RoIGVzdGltYXRvciBvZiB0aGUgdW5kZXJseWluZyBmaWVsZDoNCiQkDQpcaGF0e3p9X3AgPSBmKHhfcCwgeV9wKQ0KJCQNCg0KQW5kICRrJC1wb2ludCBtZWFucyBwcm92aWRpbmcgYW4gZXN0aW1hdG9yIG9mOg0KJCQNClxoYXR7XGVwc2lsb259X3ANCiQkDQoNCkEgcXVlc3Rpb24gaXMgaG93IHRvIGRlY2lkZSB0aGUgbnVtYmVyIG9mIG5laWdoYm9ycyB0byB1c2UgaW4gdGhlIGNhbGN1bGF0aW9uIG9mIHRoZSBrLXBvaW50IG1lYW5zLiBBcyBwcmV2aW91c2x5IGRpc2N1c3NlZCwgJGsgPSAxJCBiZWNvbWVzIGlkZW50aWNhbCB0byBWb3Jvbm9pIHBvbHlnb25zLCBhbmQgJGsgPSBuJCBiZWNvbWVzIHRoZSBnbG9iYWwgbWVhbi4NCg0KQSBzZWNvbmQgcXVlc3Rpb24gY29uY2VybnMgdGhlIHdheSB0aGUgYXZlcmFnZSBpcyBjYWxjdWxhdGVkLiBBcyB2YXJpb2dyYXBoaWMgYW5hbHlzaXMgZGVtb25zdHJhdGVzLCBpdCBpcyBwb3NzaWJsZSB0byBlc3RpbWF0ZSB0aGUgd2F5IGluIHdoaWNoIHNwYXRpYWwgZGVwZW5kZW5jZSB3ZWFrZW5zIHdpdGggZGlzdGFuY2UuIFdoeSBzaG91bGQgbW9yZSBkaXN0YW50IHBvaW50cyBiZSB3ZWlnaHRlZCBlcXVhbGx5PyBUaGUgYW5zd2VyIGlzLCB0aGVyZSBpcyBubyByZWFzb24gd2h5IHRoZXkgc2hvdWxkLCBhbmQgaW4gZmFjdCwgdmFyaW9ncmFwaGljIGFuYWx5c2lzIGVsZWdhbnRseSBzb2x2ZXMgdGhpcywgYXMgd2VsbCB0aGUgcXVlc3Rpb24gb2YgaG93IG1hbnkgcG9pbnRzIHRvIHVzZTogYWxsIG9mIHRoZW0sIHdpdGggdmFyeWluZyB3ZWlnaHRzLg0KDQpOZXh0LCB3ZSB3aWxsIGludHJvZHVjZSBrcmlnaW5nLCBhIG1ldGhvZCBmb3Igb3B0aW1hbCBwcmVkaWN0aW9uIHRoYXQgaXMgYmFzZWQgb24gdGhlIHVzZSBvZiB2YXJpb2dyYXBoaWMgYW5hbHlpc2lzLg0KDQojIyBLcmlnaW5nOiBhIG1ldGhvZCBmb3Igb3B0aW1hbCBwcmVkaWN0aW9uLg0KDQpMZXRzIGJlZ2luIGJ5IHBvc2l0aW5nIGEgc2l0dWF0aW9uIGFzIGZvbGxvd3M6DQokJA0KXGhhdHt6fV9wICsgXGhhdHtcZXBzaWxvbn1fcCA9IFxoYXR7Zn0oeF9wLCB5X3ApICsgXGhhdHtcZXBzaWxvbn1fcA0KJCQNCg0Kd2hlcmUgJFxoYXR7Zn0oeF9wLCB5X3ApJCBpcyBhIHNtb290aCBlc3RpbWF0b3Igb2YgYW4gdW5kZXJseWluZyBmaWVsZC4NCg0KV2UgYWltIHRvIHByZWRpY3QgJFxoYXR7XGVwc2lsb259X3AkIGJhc2VkIG9uIHRoZSBvYnNlcnZlZCByZXNpZHVhbHMuIFdlIHVzZSBhbiBleHByZXNzaW9uIHNpbWlsYXIgdG8gdGhlIG9uZSB1c2VkIGZvciBJRFcgYW5kIGstcG9pbnQgbWVhbnMgKHdlIHdpbGwgdXNlICRcbGFtYmRhJCBmb3IgdGhlIHdlaWdodHMgdG8gYXZvaWQgY29uZnVzaW5nIHRoZSB0aGUgd2VpZ2h0cyBpbiB2YXJpb2dyYXBoaWMgYW5hbHlzaXMpOg0KJCQNClxoYXR7XGVwc2lsb259X3AgPSBcc3VtX3tpPTF9Xm4ge1xsYW1iZGFfe3BpfVxlcHNpbG9uX2l9DQokJA0KDQpUaGF0IGlzLCAkXGhhdHtcZXBzaWxvbn1fcCQgaXMgYSBsaW5lYXIgY29tYmluYXRpb24gb2YgdGhlIHByZWRpY3Rpb24gcmVzaWR1YWxzIGZyb20gdGhlIHRyZW5kOg0KJCQNClxlcHNpbG9uX2kgPSB6X2kgLSBcaGF0e2Z9KHhfaSwgeV9pKQ0KJCQNCg0KU2luY2UgdGhlIHJlc2lkdWFscyBhcmUgcmFuZG9tLCB0aGUgcHJlZGljdGlvbiByZXNpZHVhbHMgYXJlIHJhbmRvbSB0b28sIGFuZCBpdCBpcyBwb3NzaWJsZSB0byBkZWZpbmUgdGhlIGZvbGxvd2luZyBfZXhwZWN0ZWQgbWVhbiBzcXVhcmVkIGVycm9yXywgb3IgX3ByZWRpY3Rpb24gdmFyaWFuY2VfOg0KJCQNClxzaWdtYV97XGVwc2lsb259XjIgPSBFWyhcaGF0e1xlcHNpbG9ufV9wIC0gXGVwc2lsb25faSleMl0NCiQkDQoNClRoZSBwcmVkaWN0aW9uIHZhcmlhbmNlIG1lYXN1cmVzIGhvdyBjbG9zZSwgb24gYXZlcmFnZSwgdGhlIHByZWRpY3Rpb24gZXJyb3IgaXMgdG8gdGhlIHJlc2lkdWFscy4NCg0KVGhlIHByZWRpY3Rpb24gdmFyaWFuY2UgY2FuIGJlIGRlY29tcG9zZWQgYXMgZm9sbG93czoNCiQkDQpcc2lnbWFfe1xlcHNpbG9ufV4yID0gRVtcaGF0e1xlcHNpbG9ufV9wXSArIEVbXGVwc2lsb25faV0gLSAyRVtcaGF0e1xlcHNpbG9uX2lcZXBzaWxvbn1fcF0NCiQkDQoNCkl0IHR1cm5zIG91dCAod2Ugd2lsbCBub3Qgc2hvdyBkZSBkZXRhaWxlZCBkZXJpdmF0aW9uLCBidXQgaXQgY2FuIGJlIGNvbnN1bHRlZCBbaGVyZV0oaHR0cHM6Ly9tc3UuZWR1L35hc2h0b24vY2xhc3Nlcy84NjYvcGFwZXJzL2dhdHJlbGxfb3Jka3JpZ2UucGRmKSksIHRoYXQgdGhlIGV4cHJlc2lvbiBmb3IgdGhlIHByZWRpY3Rpb24gdmFyaWFuY2UgZGVwZW5kcyBvbiB0aGUgd2VpZ2h0czoNCiQkDQpcc2lnbWFfe1xlcHNpbG9ufV4yID0gXHN1bV97aT0xfV5uIFxzdW1fe2o9MX1ebntcbGFtYmRhX3tpcH1cbGFtYmRhX3tqcH1DX3tpan19ICsgXHNpZ21hXjIgKyAyXHN1bV97aT0xfV57bn17XGxhbWJkYV97aXB9Q197aXB9fQ0KJCQNCndoZXJlICRDX3tpan0kIGlzIHRoZSBhdXRvY292YXJpYW5jZSBiZXR3ZWVuIG9ic2VydmF0aW9ucyBhdCAkaSQgYW5kICRqJCwgYW5kICRDX3tpcH0kIGlzIHRoZSBhdXRvY292YXJpYW5jZSBiZXR3ZWVuIHRoZSBvYnNlcnZhdGlvbiBhdCAkaSQgYW5kIHByZWRpY3Rpb24gbG9jYXRpb24gJHAkLg0KDQpGb3J0dW5hdGVseSBmb3IgdXMsIHRoZSBzZW1pdmFyaW9ncmFtIGFuZCB0aGUgYXV0b2NvdmFyaWFuY2UgaXMgc3RyYWlnaHRmb3J3YXJkOg0KJCQNCkNfe3p9KGgpID1cc2lnbWFeMiAtIFxoYXR7XGdhbW1hfV97en0oaCkNCiQkDQoNClRoaXMgbWVhbnMgdGhhdCwgZ2l2ZW4gdGhlIGRpc3RhbmNlICRoJCBiZXR3ZWVuICRpJCBhbmQgJGokLCBhbmQgJGkkIGFuZCAkcCQsIHdlIGNhbiB1c2UgYSBzZW1pdmFyaW9ncmFtIHRvIG9idGFpbiB0aGUgYXV0b2NvdmFyaWFuY2VzIG5lZWRlZCB0byBjYWxjdWxhdGUgdGhlIHByZWRpY3Rpb24gdmFyaWFuY2UuIFdlIGFyZSBzdGlsbCBtaXNzaW5nLCBob3dldmVyLCB0aGUgd2VpZ2h0cyAkXGxhbWJkYSQsIHdoaWNoIGFyZSBub3Qga25vd24gYSBwcmlvcmkuDQoNClRoZXNlIHdlaWdodHMgY2FuIGJlIG9idGFpbmVkIGlmIHdlIHVzZSB0aGUgZm9sbG93aW5nIHJ1bGVzOg0KDQo+IFRoZSBleHBlY3RhdGlvbiBvZiB0aGUgcHJlZGljdGlvbiBlcnJvcnMgaXMgemVybyAodW5iaWFzc2VkbmVzcykNCj4gRmluZCB0aGUgd2VpZ2h0cyAkbGFtYmRhJCB0aGF0IG1pbmltaXplIHRoZSBwcmVkaWN0aW9uIHZhcmlhbmNlIChvcHRpbWFsIGVzdGltYXRvcikuDQoNClRoaXMgbWFrZXMgc2Vuc2UsIHNpbmNlIHdlIHdvdWxkIGxpa2Ugb3VyIHByZWRpY3Rpb25zIHRvIGJlIHVuYmlhc2VkIGFuZCBhcyBwcmVjaXNlIGFzIHBvc3NpYmxlLCB0aGF0IGlzLCB0byBoYXZlIHRoZSBzbWFsbGVzdCB2YXJpYW5jZS4NCg0KQWdhaW4sIHNvbHZpbmcgdGhlIG1pbmltaXphdGlvbiBwcm9ibGVtIGlzIGJleW9uZCB0aGUgc2NvcGUgb2YgdGhpcyBwcmFjdGljZSwgYnV0IGl0IHN1ZmZpY2VzIHRvIHNheSB0aGF0IHRoZSByZXN1bHRzIGFyZSBhcyBmb2xsb3dzOg0KJCQNClxtYXRoYmZ7XGxhbWJkYX1fcCA9IFxtYXRoYmZ7Q31eey0xfVxtYXRoaXR7Y31fe3B9DQokJA0Kd2hlcmUgJFxtYXRoYmZ7Q30kIGlzIHRoZSBjb3ZhcmlhbmNlIG1hdHJpeCwgYW5kICRcbWF0aGl0e2N9X3twfSQgaXMgdGhlIGNvdmFyaWFuY2UgdmVjdG9yIGZvciBsb2NhdGlvbiAkcCQuDQoNCktyaWdpbmcgaXMga25vd24gdG8gaGF2ZSB0aGUgcHJvcGVydGllcyBvZiBCZXN0IChpbiB0aGUgc2Vuc2UgdGhhdCBpdCBtaW5pbWl6ZXMgdGhlIHZhcmlhbmNlKSBMaW5lYXIgKGJlY2F1c2Ugb2YgcHJlZGljdGlvbnMgYXJlIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHdlaWdodHMpIFVuYmlhc2VkIChzaW5jZSB0aGUgZXN0aW1hdG9ycyBvZiB0aGUgcHJlZGljdGlvbiBlcnJvcnMgYXJlIHplcm8pIEVzdGltYXRvciwgb3IgQkxVUC4NCg0KS3JpZ2luZyBpcyBpbXBsZW1lbnRlZCBpbiB0aGUgcGFja2FnZSBgZ3N0YXRgIGFzIGZvbGxvd3MuDQoNCkxldHMgZmlyc3QgY29uZHVjdCB2YXJpb2dyYXBoaWMgYW5hbHlzaXMgb2YgdGhlIHJlc2lkdWFscy4gVGhlIGZ1bmN0aW9uIGB2YXJpb2dyYW1gIHVzZXMgYXMgYW4gYXJndW1lbnQgYSBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWAgdGhhdCB3ZSBjYW4gY3JlYXRlIGFzIGZvbGxvd3MsIGJhc2VkIG9uIHRoZSB0aWR5IHRhYmxlOg0KYGBge3J9DQpXYWxrZXJfTGFrZS5zcCA8LSBXYWxrZXJfTGFrZQ0KY29vcmRpbmF0ZXMoV2Fsa2VyX0xha2Uuc3ApIDwtIH5YK1kNCmBgYA0KDQpUaGUgdmFyaW9ncmFtIG9mIHRoZSByZXNpZHVhbHMgY2FuIGJlIG9idGFpbmVkIGJ5IHNwZWNpZnlpbmcgYSB0cmVuZCBzdXJmYWNlIGluIHRoZSBmb3JtdWxhOg0KYGBge3J9DQp2YXJpb2dyYW1fdiA8LSB2YXJpb2dyYW0oViB+IFgzICsgWDJZICsgWDIgKyBYICsgWFkgKyBZICsgWTIgKyBYWTIgKyBZMywgDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IFdhbGtlcl9MYWtlLnNwKQ0KZ2dwbG90KGRhdGEgPSB2YXJpb2dyYW1fdiwgYWVzKHggPSBkaXN0LCB5ID0gZ2FtbWEpKSArDQogIGdlb21fcG9pbnQoKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbnApLCBudWRnZV95ID0gLTE1MDApICsNCiAgeGxhYigiRGlzdGFuY2UiKSArIHlsYWIoIlNlbWl2YXJpYW5jZSIpDQpgYGANCg0KWW91IGNhbiB2ZXJpZnkgdGhhdCB0aGUgc2VtaXZhcmlvZ3JhbSBhYm92ZSBjb3JyZXNwb25kcyB0byB0aGUgcmVzaWR1YWxzIGJ5IHJlcGVhdGluZyB0aGUgYW5hbHlzaXMgZGlyZWN0bHkgb24gdGhlIHJlc2lkdWFscy4gRmlyc3Qgam9pbiB0aGUgcmVzaWR1YWxzIHRvIHRoZSBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWA6DQpgYGB7cn0NCldhbGtlcl9MYWtlLnNwJGUgPC0gV0wudHJlbmQzJHJlc2lkdWFscw0KYGBgDQoNCkFuZCB0aGVuIGNhbGN1bGF0ZSB0aGUgc2VtaXZhcmlvZ3JhbSBhbmQgcGxvdDoNCmBgYHtyfQ0KdmFyaW9ncmFtX2UgPC0gdmFyaW9ncmFtKGUgfiAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gV2Fsa2VyX0xha2Uuc3ApDQpnZ3Bsb3QoZGF0YSA9IHZhcmlvZ3JhbV9lLCBhZXMoeCA9IGRpc3QsIHkgPSBnYW1tYSkpICsNCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBucCksIG51ZGdlX3kgPSAtMTUwMCkgKw0KICB4bGFiKCJEaXN0YW5jZSIpICsgeWxhYigiU2VtaXZhcmlhbmNlIikNCmBgYA0KDQpUaGUgZW1waXJpY2FsIHNlbWl2YXJpb2dyYW0gaXMgdXNlZCB0byBlc3RpbWF0ZSBhIHNlbWl2YXJpb2dyYW0gZnVuY3Rpb246DQpgYGB7cn0NCnZhcmlvZ3JhbV92LnQgPC0gZml0LnZhcmlvZ3JhbSh2YXJpb2dyYW1fdiwgbW9kZWwgPSB2Z20oIkV4cCIsICJTcGgiLCAiR2F1IikpDQp2YXJpb2dyYW1fdi50DQpgYGANCg0KVGhlIHZhcmlvZ3JhbSBmdW5jdGlvbiBwbG90cyBhcyBmb2xsb3dzOg0KYGBge3J9DQpnYW1tYS50IDwtIHZhcmlvZ3JhbUxpbmUodmFyaW9ncmFtX3YudCwgbWF4ZGlzdCA9IDEzMCkNCmdncGxvdChkYXRhID0gdmFyaW9ncmFtX3YsIGFlcyh4ID0gZGlzdCwgeSA9IGdhbW1hKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArIA0KICBnZW9tX2xpbmUoZGF0YSA9IGdhbW1hLnQsIGFlcyh4ID0gZGlzdCwgeSA9IGdhbW1hKSkgKw0KICB4bGFiKCJEaXN0YW5jZSIpICsgeWxhYigiU2VtaXZhcmlhbmNlIikNCmBgYA0KDQpgYGB7cn0NCmRmLnNwIDwtIGRmLnANCmNvb3JkaW5hdGVzKGRmLnNwKSA8LSB+WCtZDQpgYGANCg0KDQpgYGB7cn0NClYua3JpZ2VkIDwtIGtyaWdlKFYgfiBYMyArIFgyWSArIFgyICsgWCArIFhZICsgWSArIFkyICsgWFkyICsgWTMsDQogICAgICAgICAgICAgIFdhbGtlcl9MYWtlLnNwLCBkZi5zcCwgdmFyaW9ncmFtX3YudCkNCmBgYA0KDQpFeHRyYWN0IHRoZSBwcmVkaWN0aW9ucyBhbmQgcHJlZGljdGlvbiB2YXJpYW5jZSBmcm9tIHRoZSBvYmplY3QgYFYua3JpZ2VkYDoNCmBgYHtyfQ0KVi5rbSA8LSBtYXRyaXgoZGF0YSA9IFYua3JpZ2VkJHZhcjEucHJlZCwNCiAgICAgICAgICAgICAgIG5yb3cgPSAxMTksIG5jb2wgPSAxMDMsIGJ5cm93ID0gVFJVRSkNClYuc20gPC0gbWF0cml4KGRhdGEgPSBWLmtyaWdlZCR2YXIxLnZhciwNCiAgICAgICAgICAgICAgIG5yb3cgPSAxMTksIG5jb2wgPSAxMDMsIGJ5cm93ID0gVFJVRSkNCmBgYA0KDQpZb3UgY2FuIG5vdyBwbG90IHRoZSBpbnRlcnBvbGF0ZWQgZmllbGQ6DQpgYGB7cn0NClYua20ucGxvdCA8LSBwbG90X2x5KHggPSB+WC5wLCB5ID0gflkucCwgeiA9IH5WLmttLCANCiAgICAgICAgdHlwZSA9ICJzdXJmYWNlIiwgY29sb3JzID0gIllsT3JSZCIpICU+JSANCiAgbGF5b3V0KHNjZW5lID0gbGlzdCgNCiAgICBhc3BlY3Rtb2RlID0gIm1hbnVhbCIsIGFzcGVjdHJhdGlvID0gbGlzdCh4ID0gMSwgeSA9IDEsIHogPSAxKSkpDQpWLmttLnBsb3QNCmBgYA0KDQpBbHNvLCB5b3UgY2FuIHBsb3QgdGhlIGtyaWdpbmcgc3RhbmRhcmQgZXJyb3JzICh0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHByZWRpY3Rpb24gdmFyaWFuY2UpLiBUaGlzIGdpdmVzIGFuIGVzdGltYXRlIG9mIHRoZSB1bmNlcnRhaW50eSBpbiB0aGUgcHJlZGljdGlvbnM6DQpgYGB7cn0NClYuc20ucGxvdCA8LSBwbG90X2x5KHggPSB+WC5wLCB5ID0gflkucCwgeiA9IH5zcXJ0KFYuc20pLCANCiAgICAgICAgdHlwZSA9ICJzdXJmYWNlIiwgY29sb3JzID0gIllsT3JSZCIpICU+JSANCiAgbGF5b3V0KHNjZW5lID0gbGlzdCgNCiAgICBhc3BlY3Rtb2RlID0gIm1hbnVhbCIsIGFzcGVjdHJhdGlvID0gbGlzdCh4ID0gMSwgeSA9IDEsIHogPSAxKSkpDQpWLnNtLnBsb3QNCmBgYA0KDQpXaGVyZSBhcmUgcHJlZGljdGlvbnMgbW9yZS9sZXNzIHByZWNpc2U/